依赖注入 定义 在后端领域,依赖注入(DI),控制反转(IoC)等概念是老生常谈的设计模式,它们用于解决一系列的面向对象编程问题。
对于依赖注入而言,需要遵循依赖倒转原则(Dependence Inversion Priciple, DIP):
高层模块不应该依赖低层模块。两个都应该依赖抽象(提取共性,描述特性,模块间解耦)
抽象不应该依赖细节,细节应该依赖抽象(抽象服务类置于底层,细节服务类继承抽象服务类)
针对接口编程,不要针对实现编程(遵循SOLID原则,多态、泛型编程等)
为什么需要依赖注入 前端工程化、模块化的时间相比于后端而言还是比较短的。在此之前,前端的设计方式主要是每个页面都有其对应的JS文件,分别负责这个页面上的交互操作。
然而随着项目的越来越大,这样的文件结构也显得越来越臃肿,特别是由于很多功能的共性没有提取出来,形成模块化,因而产生了很多重复性的代码。这样的结构存在很大的弊端:
一旦需要一些共性方面的修改,就需要逐个文件去检查,修改对应代码,费时费力
重复性的代码增大了项目的体积,代码却不能复用
每次引入一大堆JS文件,需要执行后存入内存方可使用,效率低下
因此,急需一种机制来负责管理这些可复用的底层的操作,单例服务以及共有的特性 。
传统的依赖注入 传统的依赖注入采取AMD、CMD、CommanJS和UMD等模式。
一个AMD的依赖管理器可以大致理解为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 var ModuleManager = (function ( ) { var _modules = {}; function getModule (name ) { return _modules[name] || {}; } function define (name, deps, fn ) { if (typeof name !== 'string' ) { throw new TypeError ('name should be string.' ); } if (_modules[name]) { throw new Error ('This module has been already declared.' ); } if (!deps instanceof Array ) { throw new TypeError ('deps should be Array<String>.' ); } if (!fn instanceof Function ) { throw new TypeError ('fn should be function.' ); } var depsModules = []; for (var i = 0 , len = deps.length; i < len; i++) { depsModules.push(getModule(deps[i])); } _modules[name] = fn.apply(null , depsModules); return true ; } function require (deps, fn ) { if (!deps instanceof Array ) { throw new TypeError ('deps should be Array<String>.' ); } if (!fn instanceof Function ) { throw new TypeError ('fn should be function.' ); } var depsModules = []; for (var i = 0 , len = deps.length; i < len; i++) { depsModules.push(getModule(deps[i])); } fn.apply(null , depsModules); } return { define: define, require : require , }; })();
通过这段代码可以看出,传统的依赖注入本质上是把依赖存在内存中,然后根据其名称导出,再注入到回调函数中供其运行。
更好的实践 Angular2+内置了依赖注入的机制,利用@Injectable()来修饰一个服务类。
1 2 3 4 5 6 7 8 @Injectable() export class SomeService { constructor () {} dosth () { } }
然后在Component或Module的providers属性中去声明提供的服务类,从而在这一层生成一个实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Component({ selector: '...' , template: `` , providers: [SomeService], }) @NgModule({ imports: [], exports: [], declarations: [], providers: [SomeService], })
最后,在Component的constructor中,就可以将其作为依赖导入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class TemplateComponent { constructor (private _someService: SomeService) { this ._someService.dosth(); } } class TemplateComponent { constructor (SomeService) { this .SomeService = SomeService; this ._someService = this .SomeService; } }
对于一个Component中使用的依赖,其寻径过程遵循就近使用的原则:
首先寻找自身Component修饰器中是否有对应provider
自身没有,向上层父Component寻找
父Component逐层向上,一直到其所在的Module层
自身Module层没有,寻找所在Module的上层Component或Module
一直到最高层的App.Component中都没有找到,说明该provider尚未注册,抛出错误
需要注意的是,这些依赖本身就是一个个实例,所以如果其中有一些不能复用的属性(虽然从设计上而言,应当避免这种情况),应当在所操作的Component上新注册一个该服务的实例,从而达到属性不共享。
Vue中实现高复用的依赖注入 1.简单粗暴法,Vue.use() Vue.use()方法允许将具体的服务类实例挂载到全局对象的原型链上,调用时候直接去全局对象上寻找相应依赖。这种方法虽然不甚美观,而且随着项目越来越大,全局对象也会越来越臃肿,但确实是非常简单实现方式。
2.利用Vuex 利用Vuex.Store的Modules属性,可以使用一个独特的DependencyStore来存放依赖。在此基础之上,可以实现模块的分类与分级,从而将其层级化,方便调用。
3.第三方库InversifyJS InversifyJS的使用方式类似于Angular的依赖注入模式:
创建声明
1 2 3 4 5 6 7 8 9 10 11 12 13 export interface Warrior { fight(): string; sneak(): string; } export interface Weapon { hit(): string; } export interface ThrowableWeapon { throw (): string; }
1 2 3 4 5 6 7 8 const TYPES = { Warrior: Symbol .for("Warrior" ), Weapon: Symbol .for("Weapon" ), ThrowableWeapon: Symbol .for("ThrowableWeapon" ) }; export { TYPES };
编写实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @injectable() class Katana implements Weapon { public hit() { return "cut!" ; } } @injectable() class Shuriken implements ThrowableWeapon { public throw () { return "hit!" ; } } @injectable() class Ninja implements Warrior { private _katana: Weapon; private _shuriken: ThrowableWeapon; public constructor ( @inject(TYPES.Weapon) katana: Weapon, @inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon ) { this ._katana = katana; this ._shuriken = shuriken; } public fight() { return this ._katana.hit(); } public sneak() { return this ._shuriken.throw(); } } export { Ninja, Katana, Shuriken };
1 2 3 4 5 6 7 8 @injectable() class Ninja implements Warrior { @inject(TYPES.Weapon) private _katana: Weapon; @inject(TYPES.ThrowableWeapon) private _shuriken: ThrowableWeapon; public fight() { return this ._katana.hit(); } public sneak() { return this ._shuriken.throw(); } }
创建一个容器,放置服务类
1 2 3 4 5 6 7 8 9 10 11 12 import { Container } from "inversify" ;import { TYPES } from "./types" ;import { Warrior, Weapon, ThrowableWeapon } from "./interfaces" ;import { Ninja, Katana, Shuriken } from "./entities" ;const myContainer = new Container();myContainer.bind<Warrior>(TYPES.Warrior).to(Ninja); myContainer.bind<Weapon>(TYPES.Weapon).to(Katana); myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken); export { myContainer };
注入依赖
1 2 3 4 5 6 import { myContainer } from "./inversify.config" ;import { TYPES } from "./types" ;import { Warrior } from "./interfaces" ;const ninja = myContainer.get<Warrior>(TYPES.Warrior);
4. 利用TypeScript实现Ultimate Mode 首先声明一个ModuleManager模块来负责依赖管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const ModuleManager = (function ( ) { (window as any ).Modules = {}; const modules = window .Modules; function BKDRHash (str: string ): number { const seed: number = 31 ; let hash: number = 0 , index: number = 0 ; while (str[index]) { hash = hash * seed + str.charCodeAt(index++); hash &= 0xFFFFFFFF ; } return hash; } function addModule (target: any ): void { const hash = BKDRHash(target.toString().replace(/^function\s(.*)?\s.*/ , '$1' )); if (!modules[hash]) { modules[hash] = new target(); } } function getModule (target: any ): any { const hash = BKDRHash(target.toString().replace(/^function\s(.*)?\s.*/ , '$1' )); if (!modules[hash]) { modules[hash] = new target(); } return modules[hash]; } return { addModule, getModule, } }());
然后需要利用TypeScript提供的Decorator修饰器,来对模块与注入的依赖进行管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const Injectable = (): ClassDecorator => (target: Function ): any => { ModuleManager.addModule(target); }; @Injectable() export class SomeModule {}const Provided = (module : any): PropertyDecorator => (target: Object , name : string | symbol): any => { return { configurable: true , enumerable: false , set : () => {}, get : () => { return ModuleManager.getModule(module ); }, }; }; export class Example { @Provided(SomeModule) private _someModule: SomeModule; }